// Copyright 2006 Team AWESOME. All Rights Reserved.
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * Runs a command-line application for counting the number of occurrences of all
 * words or phrases in a file.
 */
public class AIMDump {

  private static class NullOutputStream extends OutputStream {
    public void write(int b) throws IOException {
      // nothing
    }
  }

  /**
   * Run a command-line application to count the number of occurrences of all
   * words or phrases in a file.
   * 
   * @param args the command-line arguments
   */
  public static void main(String[] args) {
    try {
      PrintStream out;
      if (args.length > 0) {
        out = new PrintStream(new NullOutputStream());
      } else {
        out = System.out;
      }

      RandomAccessFile input = new RandomAccessFile("AIM.out", "r");
      input.seek(input.length() - 4);
      int tailSize = input.readInt();
      input.seek(input.length() - tailSize);

      // Print corpus results
      // 1: print date of time of construction
      Date indexDate = new Date(input.readLong());
      out.println(indexDate);

      // 2: print variable break characters
      String varBreakChars = "";
      for (byte b = input.readByte(); b != 0; b = input.readByte()) {
        varBreakChars += (char) (b & 0xFF);
      }
      out.println("Variable break characters=" + varBreakChars);
      CharacterAnalyzer analyzer = new CharacterAnalyzer(varBreakChars);

      // 3: print stop word file name
      String stopFileName = input.readUTF();
      if (stopFileName.length() > 0) {
        out.println("Stop file: " + stopFileName);
      }

      // 4: print information about each file
      LinkedList fileInfos = new LinkedList();
      int corpusBytes = 0, corpusWords = 0, corpusLines = 0;
      for (String file = input.readUTF(); file.length() > 0; file = input.readUTF()) {
        FileInfo info = new FileInfo(file);
        info.setBytes(input.readInt());
        info.setWords(input.readInt());
        info.setLines(input.readInt());
        corpusBytes += info.getBytes();
        corpusWords += info.getWords();
        corpusLines += info.getLines();
        out.println(info.getFilePath() + ": " + info.getBytes() + " bytes, "
            + info.getWords() + " words, " + info.getLines() + " lines");
        fileInfos.addLast(info);
      }

      // 5: computed corpus stats
      out.println("Corpus Total: " + corpusBytes + " bytes, " + corpusWords
          + " words, " + corpusLines + " lines, " + fileInfos.size() + " files");

      // Search terms
      for (int i = 0; i < args.length; ++i) {
        String word = WordParser.Util.toLowerCase(args[i]);
        input.seek(0);
        search(word, input, fileInfos, analyzer);
      }

      close(input);
      // normal status code 0 exit
      return;
    } catch (FileNotFoundException e) {
      System.err.println("Error: could not open index file 'AIM.out'");
    } catch (IOException e) {
      System.err.println("Error: could not read from index file 'AIM.out'");
    } catch (OutOfMemoryError e) {
      System.err.println("Error: out of memory; the corpus may be too large");
    } catch (Throwable e) {
      System.err.println("Error: program terminated unexpectedly");
    }
    // error exit
    System.exit(-1);
  }

  private static void close(InputStream in) {
    try {
      if (in != null) {
        in.close();
      }
    } catch (IOException e) {
      // ignore; what are you going to do?
    }
  }

  private static void close(RandomAccessFile in) {
    try {
      if (in != null) {
        in.close();
      }
    } catch (IOException e) {
      // ignore; what are you going to do?
    }
  }

  private static void search(String word, RandomAccessFile input,
      LinkedList fileInfos, CharacterAnalyzer analyzer) throws IOException {
    System.out.println(word + ":");
    boolean foundMatch = false;
    int[] hashes = Hasher.computeHashes(word, LineInfo.STRONG_HASHES);
    BitReader reader = new BitReader(input);
    for (Iterator it = fileInfos.iterator(); it.hasNext();) {
      FileInfo info = (FileInfo) it.next();
      DataInputStream dataInput = null;
      try {
        File file = new File(info.getFilePath());
        if (file.exists() && file.isFile() && file.canRead()) {
          RandomAccessFile inFile = new RandomAccessFile(file, "r");
          if (inFile.length() == info.getBytes()) {
            InputStream inStream = new FileInputStream(info.getFilePath());
            inStream = new BufferedInputStream(inStream);
            dataInput = new DataInputStream(inStream);
          }
        }
      } catch (IOException e) {
        // couldn't find the original, just ignore
      }
      for (int i = 0, c = info.getLines(); i < c; ++i) {
        String theLine = null;
        if (dataInput != null) {
          try {
            theLine = dataInput.readLine();
          } catch (IOException e) {
            close(dataInput);
            dataInput = null;
          }
        }
        int count = tryMatchLine(hashes, reader);
        if (count > 0 && theLine != null) {
          // validate against corpus
          theLine = WordParser.Util.toLowerCase(theLine);
          count = 0;

          for (int pos = theLine.indexOf(word); pos >= 0; pos = theLine.indexOf(
              word, pos + 1)) {
            if (pos > 0 && !analyzer.isBreakChar(theLine.charAt(pos - 1))) {
              continue;
            }
            int end = pos + word.length();
            if (end < theLine.length()
                && !analyzer.isBreakChar(theLine.charAt(end))) {
              continue;
            }
            ++count;
          }
        }
        for (int j = 0; j < count; ++j) {
          foundMatch = true;
          System.out.println(info.getFilePath() + '(' + (i + 1) + ')');
        }
      }
      close(dataInput);
    }
    if (!foundMatch) {
      System.out.println("(no matches)");
    }
    System.out.println();
  }

  private static int tryMatchLine(int[] hashes, BitReader reader)
      throws IOException {
    int count = 0;
    boolean failed = false;
    outer : while (reader.read()) {
      int nBits = Indexer.MIN_FILTER_SIZE;
      while (reader.read()) {
        nBits <<= 1;
      }
      int nHash = (count == 0) ? LineInfo.STRONG_HASHES : LineInfo.WEAK_HASHES;
      int[] filterPos = new int[nHash];
      for (int i = 0; i < nHash; ++i) {
        filterPos[i] = hashes[i] & (nBits - 1);
      }
      Arrays.sort(filterPos);

      int bitPos = 0;
      for (int i = 0; i < nHash; ++i) {
        int nextBit = filterPos[i];
        // advance to next bit to check
        while (bitPos < nextBit) {
          ++bitPos;
          reader.read();
        }
        // check the bit
        if (bitPos == nextBit) {
          // we fail this filter
          ++bitPos;
          if (!reader.read()) {
            failed = true;
          }
        }
      }

      // if we haven't failed yet, we just passed a filter; inc the count
      if (!failed) {
        ++count;
      }

      // read to end of filter
      while (bitPos < nBits) {
        reader.read();
        ++bitPos;
      }
    }
    return count;
  }

  /**
   * Not instantiable.
   */
  private AIMDump() {
  }

}
